#include <app.hpp>

#include <random>
#include <ctime>

#define TETRIS_MAP_WIDTH   10
#define TETRIS_MAP_HEIGHT  20
#define TETRIS_ITEM_WIDTH  18
#define TETRIS_ITEM_HEIGHT 18
#define TETRIS_ID_NEXT_STEP (APP_USER_EVENT_ID+1)

enum class eState : cvs::uint8 {
  Start,Runing,Finish,Win
};

struct Point { int x; int y; };
struct Size { int w; int h; };

class Shape : public AppSprite {
  public:
    Shape(App *app) : AppSprite(app) {
      bitmap_tiles_ = 0;
      visible_ = true;
      type_ = 1;
      type_r_ = 0;
      step_ = 1.f;
    }
    virtual ~Shape() {}
  public:
    // 1-7
    void set_type(int t, int r) {
      type_ = t;
      type_r_ = r;
      set_empty();
      switch (t) {
        case 1: {
          if (r % 2 == 0) {
            maps_[0][0] = 1;
            maps_[1][0] = 1;
            maps_[2][0] = 1;
            maps_[3][0] = 1;
          } else {
            maps_[0][0] = 1;
            maps_[0][1] = 1;
            maps_[0][2] = 1;
            maps_[0][3] = 1;
          }
        } break;
        case 2: {
          if (r % 4 == 0) {
            maps_[0][1] = 2;
            maps_[1][0] = 2;
            maps_[1][1] = 2;
            maps_[1][2] = 2;
          } else if (r % 4 == 1) {
            maps_[0][1] = 2;
            maps_[1][1] = 2;
            maps_[2][1] = 2;
            maps_[1][2] = 2;
          } else if (r % 4 == 2) {
            maps_[1][0] = 2;
            maps_[1][1] = 2;
            maps_[2][1] = 2;
            maps_[1][2] = 2;
          } else if (r % 4 == 3) {
            maps_[0][1] = 2;
            maps_[1][0] = 2;
            maps_[1][1] = 2;
            maps_[2][1] = 2;
          }
        } break;
        case 3: {
          maps_[0][0] = 3;
          maps_[0][1] = 3;
          maps_[1][0] = 3;
          maps_[1][1] = 3;
        } break;
        case 4: {
          if (r % 4 == 0) {
            maps_[0][0] = 4;
            maps_[1][0] = 4;
            maps_[2][0] = 4;
            maps_[2][1] = 4;
          } else if (r % 4 == 1) {
            maps_[1][0] = 4;
            maps_[1][1] = 4;
            maps_[1][2] = 4;
            maps_[2][0] = 4;
          } else if (r % 4 == 2) {
            maps_[0][1] = 4;
            maps_[1][1] = 4;
            maps_[2][1] = 4;
            maps_[0][0] = 4;
          } else if (r % 4 == 3) {
            maps_[0][2] = 4;
            maps_[1][0] = 4;
            maps_[1][1] = 4;
            maps_[1][2] = 4;
          }
        } break;
        case 5: {
          if (r % 4 == 0) {
            maps_[0][1] = 5;
            maps_[1][1] = 5;
            maps_[2][1] = 5;
            maps_[2][0] = 5;
          } else if (r % 4 == 1) {
            maps_[1][0] = 5;
            maps_[2][0] = 5;
            maps_[2][1] = 5;
            maps_[2][2] = 5;
          } else if (r % 4 == 2) {
            maps_[0][1] = 5;
            maps_[0][2] = 5;
            maps_[1][1] = 5;
            maps_[2][1] = 5;
          } else if (r % 4 == 3) {
            maps_[1][0] = 5;
            maps_[1][1] = 5;
            maps_[1][2] = 5;
            maps_[2][2] = 5;
          }
        } break;
        case 6: {
          if (r % 2 == 0) {
            maps_[0][1] = 6;
            maps_[1][0] = 6;
            maps_[1][1] = 6;
            maps_[2][0] = 6;
          } else {
            maps_[1][0] = 6;
            maps_[1][1] = 6;
            maps_[2][1] = 6;
            maps_[2][2] = 6;
          }
        } break;
        case 7: {
          if (r % 2 == 0) {
            maps_[0][0] = 7;
            maps_[1][0] = 7;
            maps_[1][1] = 7;
            maps_[2][1] = 7;
          } else {
            maps_[1][1] = 7;
            maps_[1][2] = 7;
            maps_[2][0] = 7;
            maps_[2][1] = 7;
          }
        } break;
      }
    }
    int type() const { return type_; }
    int type_r() const { return type_r_; }
    int data() const { return type_; }
    void rotate(int tr = -1) {
      if (tr < 0) {
        tr = type_r_ + 1;
        if (tr >= 4) tr = 0;
      }
      set_type(type_, tr);
    }
    void set_visible(bool v) { visible_ = v; }
    void set_tiles(cvs::c2d::IBitmap *bitmap, int num) {
      if (num < 0 || !bitmap) return;
      bitmap_tiles_ = bitmap;
      auto sz = bitmap_tiles_->size();
      tile_size_.w = sz.w / num;
      tile_size_.h = sz.h;
    }
    void start(Uint32 t, cvs::dtype step) {
      set_visible(true);
      remove_timer();
      add_timer(t);
      step_ = step;
      pt_.y = 0.f;
      pt_.x = TETRIS_MAP_WIDTH / 2 - 1;
    }
    void stop() {
      remove_timer();
      set_visible(false);
    }
    void move_left() { pt_.x-=1; }
    void move_right() { pt_.x+=1; }
    void move_bottom() { pt_.y+=1; }
    void point(std::vector<cvs::Point> &pts) {
      for (int j = 0; j < 4; ++j) {
        for (int i = 0; i < 4; ++i) {
          if (maps_[j][i]) {
            cvs::Point pt;
            pt.x = i + pt_.x;
            pt.y = j + pt_.y;
            pts.push_back(std::move(pt));
          }
        }
      }
    }
  protected:
    virtual void timer(SDL_TimerID id, Uint32 interval, void *param) {
      if (timer_id_ != id) return;
      app_->do_user_event(TETRIS_ID_NEXT_STEP);
    }
    virtual void draw(cvs::c2d::ITarget *target) override {
      if (!visible_ || !target || !bitmap_tiles_) return;
      
      cvs::Point pt;
      cvs::Rect rc;
      rc.y = 0.f;
      rc.w = tile_size_.w; rc.h = tile_size_.h;
      for (int j = 0; j < 4; ++j) {
        for (int i = 0; i < 4; ++i) {
          auto d = maps_[j][i];
          if (d) {
            rc.x = (d-1) * tile_size_.w;
            pt.x = pos_.x + i * tile_size_.w + pt_.x * tile_size_.w;
            pt.y = pos_.y + j * tile_size_.h + pt_.y * tile_size_.h;
            target->draw_bitmap(bitmap_tiles_, pt, &rc);
          }
        }
      }
    }
  protected:
    void set_empty() {
      for (int i = 0; i < 4; ++i)
        for (int j = 0; j < 4; ++j)
          maps_[i][j] = 0;
    }
  protected:
    int type_;
    int type_r_;
    int maps_[4][4];
    bool visible_;
    cvs::c2d::IBitmap *bitmap_tiles_;
    cvs::Size tile_size_;
    cvs::dtype step_;
    cvs::Point pt_;
};

class Tetris : public App {
  public:
    Tetris() : shape_cur_(this), shape_next_(this) {
      offset_.x = 28.f;
      offset_.y = 31.f;
      state_ = eState::Start;

      ctrl_play_ = 0;
      level_ = 1;
      grade_ = 0;
      pause_ = false;
    }
    virtual ~Tetris() {}
  protected:
    virtual cvs::Result create_resource() override {
      auto rst = render_->create_shape_rect(cvs::Rect(), cvs::Size(), &shape_rect_);
      
      music_level_up_ = load_music("tetris/level_up.wav");
      music_cancel_ = load_music("tetris/cancel.wav");
      music_drop_ = load_music("tetris/drop.wav");
      music_pause_ = load_music("tetris/pause.wav");
      music_move_ = load_music("tetris/move.wav");
      return rst;
    }
    virtual void release_resource() override {
      cvs::SaveDecref(shape_rect_); shape_rect_ = 0;
      unload_music(music_level_up_);
      unload_music(music_cancel_);
      unload_music(music_drop_);
      unload_music(music_pause_);
      unload_music(music_move_);
    }
    virtual cvs::Result create_target_resource() override {
      auto rst = target_->create_solid_color_brush(cvs::Color(), &brush_);
      if (cvs::Succeeded(rst))
        rst = load_bitmaps();
      if (cvs::Succeeded(rst))
        rst = load_ctrls();
      return rst;
    }
    virtual void release_target_resource() override {
      cvs::SaveDecref(brush_); brush_ = 0;
      unload_ctrls();
      unload_bitmaps();
    }
  protected:
    virtual void on_draw() override {
      target_->draw_bitmap(bitmap_back_);
      switch (state_) {
        case eState::Start:
          draw_start();
          break;
        case eState::Runing:
          draw_runing();
          break;
        case eState::Win:
          draw_win();
          break;
        case eState::Finish:
          draw_finish();
          break;
      }
      draw_info();
    }
    void draw_start() {}
    void draw_runing() {
      cvs::Point pt;
      cvs::Rect rc;
      rc.y = 0.f;
      rc.w = TETRIS_ITEM_WIDTH; rc.h = TETRIS_ITEM_HEIGHT;
      for (int j = 0; j < TETRIS_MAP_HEIGHT; ++j) {
        for (int i = 0; i < TETRIS_MAP_WIDTH; ++i) {
          auto d = maps_[j][i];
          if (d) {
            rc.x = (d-1) * TETRIS_ITEM_WIDTH;
            pt.x = offset_.x + i * TETRIS_ITEM_WIDTH;
            pt.y = offset_.y + j * TETRIS_ITEM_HEIGHT;
            target_->draw_bitmap(bitmap_tiles_, pt, &rc);
          }
        }
      }
    }
    void draw_win() {}
    void draw_finish() {}
    void draw_info() {
      cvs::Rect rc(230.f, 120.f, 90.f, 30.f);
      cvs::Color color(1.f,0.f,0.14f);
      cvs::dtype size = 20.f;

      rc.y += 50.f;
      draw_text(L"Level: ", rc, size, color);
      rc.y += 30.f;
      draw_text(std::to_wstring(level_), rc, size, color);

      rc.y += 50.f;
      draw_text(L"Grade: ", rc, size, color);
      rc.y += 30.f;
      draw_text(std::to_wstring(grade_), rc, size, color);

      rc.y += 50.f;
      draw_text(L"Time: ", rc, size, color);
      rc.y += 30.f;
      auto time = (time_end_-time_begin_)/1000;
      draw_text(std::to_wstring(time), rc, size, color);

      if (pause_) {
        rc = window_rect();
        draw_text(L"Paused", rc, 30.f, color);
      }
    }
  protected:
    virtual void on_mouse_click(const SDL_MouseButtonEvent &evt) override {
      if (evt.state == SDL_PRESSED && evt.button == SDL_BUTTON_LEFT) {

      }
    }
    virtual void on_keyboard(const SDL_KeyboardEvent &evt) override {
      if (evt.state == SDL_PRESSED) {
        if (evt.keysym.sym == SDLK_LEFT) {
          if (can_move(1)) {
            shape_cur_.move_left();
            invaldate();
          }
        } else if (evt.keysym.sym == SDLK_RIGHT) {
          if (can_move(2)) {
            shape_cur_.move_right();
            invaldate();
          }
        } else if (evt.keysym.sym == SDLK_UP) {
          if (can_rotate()) {
            shape_cur_.rotate();
            play_sound(music_move_);
            invaldate();
          }
        } else if (evt.keysym.sym == SDLK_DOWN) {
          if (can_move(3)) {
            shape_cur_.move_bottom();
            invaldate();
          }
        } else if (evt.keysym.sym == SDLK_SPACE) {
          move_down();
          invaldate();
        }

        else if (evt.keysym.sym == SDLK_p) {
          pause_ = !pause_;
          play_sound(music_pause_);
          invaldate();
        }
      }
    }
    virtual void on_user(Sint32 id, void *data1, void *data2) override {
      if (id == TETRIS_ID_NEXT_STEP) {
        if (pause_) return;
        if (can_move(3)) {
          shape_cur_.move_bottom();
        } else {
          fixed_shape();
        }
        invaldate();
      }
    }
    virtual void on_timer(SDL_TimerID id, Uint32 interval, void *param) override {
      if (id == timer_time_) {
        if (pause_) return;
        time_end_ = SDL_GetTicks();
        invaldate();
      }
    }
  protected:
    cvs::Result load_bitmaps() {
      auto rst = create_bitmap_from_file("tetris/background.png", &bitmap_back_);
      if (cvs::Succeeded(rst))
        rst = create_bitmap_from_file("tetris/tiles.png", &bitmap_tiles_);
      return rst;
    }
    void unload_bitmaps() {
      cvs::SaveDecref(bitmap_back_); bitmap_back_ = 0;
      cvs::SaveDecref(bitmap_tiles_); bitmap_tiles_ = 0;
    }
    cvs::Result load_ctrls() {
      auto size = window_size();
      auto ctrl_play = new AppControl(this);
      if (ctrl_play) {
        cvs::dtype w = 100.f;
        cvs::dtype h = 50.f;
        cvs::dtype x = size.w / 2.f - w / 2.f;
        cvs::dtype y = size.h / 2.f - h / 2.f;
        cvs::Rect rc(x, y, w, h);
        ctrl_play->set_rect(rc);
        ctrl_play->set_radius(cvs::Size(5.f, 5.f));
        ctrl_play->set_use_back_color(true);
        ctrl_play->set_text(L"Play");
        ctrl_play->normal.back_color = cvs::Color(1.f,0.66f,0.14f);
        ctrl_play->normal.text_color = cvs::Color(1.f,1.f,0.96f,0.9f);
        ctrl_play->normal.font_format.size = 20.f;
        ctrl_play->normal.text_format.halign = cvs::c2d::TextAlignHor::Center;
        ctrl_play->normal.text_format.valign = cvs::c2d::TextAlignVer::Middle;
        ctrl_play->hot.back_color = cvs::Color(1.f,0.59f,0.f);
        ctrl_play->hot.border_color = cvs::Color(0.f,1.f,0.f);
        ctrl_play->hot.text_color = cvs::Color(1.f,1.f,0.96f,0.9f);
        ctrl_play->hot.font_format.size = 20.f;
        ctrl_play->hot.text_format.halign = cvs::c2d::TextAlignHor::Center;
        ctrl_play->hot.text_format.valign = cvs::c2d::TextAlignVer::Middle;
        ctrl_play->pushed.back_color = cvs::Color(95.f,0.5f,0.02f);
        ctrl_play->pushed.text_color = cvs::Color(1.f,1.f,0.96f,0.9f);
        ctrl_play->pushed.font_format.size = 20.f;
        ctrl_play->pushed.text_format.halign = cvs::c2d::TextAlignHor::Center;
        ctrl_play->pushed.text_format.valign = cvs::c2d::TextAlignVer::Middle;
        ctrl_play->set_callback(std::bind(&Tetris::on_ctrl_play, this, std::placeholders::_1));
        ctrl_play_ = ctrl_play;
      }
      shape_cur_.set_tiles(bitmap_tiles_, 8);
      shape_cur_.set_visible(false);
      shape_next_.set_tiles(bitmap_tiles_, 8);
      shape_next_.set_pos(250.f, 50.f);
      shape_next_.set_type(random(1, 7), 0);
      return CVS_S_Ok;
    }
    void unload_ctrls() {
      if (ctrl_play_) {
        delete ctrl_play_;
        ctrl_play_ = 0;
      }
    }
  protected:
    // 0:cur left:1, right:2, bottom:3, 4:drop
    bool can_move(int d) {
      std::vector<cvs::Point> pts;
      shape_cur_.point(pts);
      if (d==0) {
      } else if (d == 1) {
        for (auto &p : pts)
          p.x -= 1;
      } else if (d==2) {
        for (auto &p : pts)
          p.x += 1;
      } else if (d==3) {
        for (auto &p : pts)
          p.y += 1;
      } else if (d==4) {
      }

      for (auto &p : pts) {
        if (p.x < 0 || p.x > TETRIS_MAP_WIDTH - 1
            || p.y < 0 || p.y > TETRIS_MAP_HEIGHT - 1) {
          return false;
        }
      }

      for (int j = 0; j < TETRIS_MAP_HEIGHT; ++j) {
        for (int i = 0; i < TETRIS_MAP_WIDTH; ++i) {
          if (maps_[j][i]) {
            for (auto &p : pts) {
              if (p.x == i && p.y == j)
                return false;
            }
          }
        }
      }

      return true;
    }
    bool can_rotate() {
      auto tr = shape_cur_.type_r();
      shape_cur_.rotate();
      auto r = can_move(0);
      shape_cur_.rotate(tr);
      return r;
    }
    void move_down() {
      while (can_move(3)){
        shape_cur_.move_bottom();
      }
      do_user_event(TETRIS_ID_NEXT_STEP);
    }
    void fixed_shape() {
      play_sound(music_drop_);

      std::vector<cvs::Point> pts;
      shape_cur_.point(pts);
      int count = 0;
      for (auto &p : pts) {
        if (maps_[(int)p.y][(int)p.x] == 0) {
          maps_[(int)p.y][(int)p.x] = shape_cur_.data();
          ++count;
        }
      }

      if (count == pts.size()) {
        auto num = cancel();
        add_grade(num);
        gen_shape();
      } else {
        shape_cur_.stop();
        state_ = eState::Start;
        ctrl_play_->set_visible(true);
        remove_timer(timer_time_);
        timer_time_ = 0;
      }
    }
    void clear_line(int line) {
      for (int j = line; j > 0; --j) {
        for (int i = 0; i < TETRIS_MAP_WIDTH; ++i) {
          maps_[j][i] = maps_[j-1][i];
        }
      }
    }
    int cancel() {
      int count = 0;
      for (int j = TETRIS_MAP_HEIGHT - 1; j >= 0; --j) {
        bool done = true;
        for (int i = 0; i < TETRIS_MAP_WIDTH; ++i) {
          if (maps_[j][i] == 0) {
            done = false;
            break;
          }
        }
        if (done) {
          clear_line(j);
          ++count;
          play_sound(music_cancel_);
          count += cancel();
          return count;
        }
      }
      return count;
    }
    void add_grade(int line) {
      int grade = 0;
      if (line <= 0) grade = 0;
      else if (line == 1) grade = 10;
      else if (line == 2) grade = 30;
      else if (line == 3) grade = 60;
      else grade = line * 20 + 20;
      grade_ += grade;
      
      auto level = grade_ / 1000 + 1;
      if (level_ != level) {
        level_ = level;
        play_sound(music_level_up_);
      }
    }
    void gen_shape() {
      shape_cur_.set_type(shape_next_.type(), 0);
      shape_cur_.move_to(offset_.x, offset_.y);
      shape_cur_.start(get_level_timer(), 1.0f);
      shape_next_.set_type(random(1, 7), 0);
    }
    Uint32 get_level_timer() {
      return 1000 / level_;
    }
    void on_ctrl_play(AppControl *ctrl) {
      ctrl->set_visible(false);
      empty_map();
      state_ = eState::Runing;
      gen_shape();
      invaldate();
      timer_time_ = add_timer(1000);
      time_begin_ = SDL_GetTicks();
      time_end_ = SDL_GetTicks();
      play_sound(music_pause_);
      grade_ = 0;
      level_ = 1;
    }
  protected:
    void empty_map() {
      for (int j = 0; j < TETRIS_MAP_HEIGHT; ++j) {
        for (int i = 0; i < TETRIS_MAP_WIDTH; ++i) {
          maps_[j][i] = 0;
        }
      }
    }
  protected:
    eState state_;
    int level_;
    int grade_;
    int maps_[TETRIS_MAP_HEIGHT][TETRIS_MAP_WIDTH];
    cvs::Point offset_;

    cvs::c2d::IShapeRect *shape_rect_;
    cvs::c2d::ISolidColorBrush *brush_;

    cvs::c2d::IBitmap *bitmap_back_;
    cvs::c2d::IBitmap *bitmap_tiles_;

    SDL_TimerID timer_time_;
    SDL_TimerID timer_toplay_;
    Uint32 time_begin_;
    Uint32 time_end_;

    AppControl *ctrl_play_;
    Shape shape_cur_;
    Shape shape_next_;

    bool pause_;

    Mix_Music *music_level_up_;
    Mix_Music *music_drop_;
    Mix_Music *music_move_;
    Mix_Music *music_cancel_;
    Mix_Music *music_pause_;
};

int main(int argc, char *argv[]) {
  Tetris app;

  auto rst = app.init();
  if (cvs::Succeeded(rst))
    rst = app.create_window("Tetris", cvs::Size(320.f, 480.f), SDL_WINDOW_SHOWN);

  if (cvs::Succeeded(rst))
    app.run(APP_RUN_SLOWEST);

  app.uninit();
  return 0;
}